package cz.mff.cuni.dpp.liboptions;

import java.util.HashSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Základní jednotka konfigurace, nikdy se nepoužívá přímo, pouze skrz
 * podtřídy.
 * 
 * @param <T>
 */
public abstract class Option<T> {

	public static final String SHORT_OPTION_PREFIX = "-";
	public static final String LONG_OPTION_PREFIX = "--";
	public static final String LONG_OPTION_VALUE_DELIMITER = "=";
	public static final String ARGUMENTS_DELIMITER = "--";

	/**
	 * Aliasy s touto nebo mensi delkou jsou povazovany za kratke, vsechnz
	 * ostatni za dlouhe
	 */
	private static final int MAX_SHORT_ALIAS_LENGHT = 1;

	private static final Logger logger = LoggerFactory.getLogger(Option.class);

	private final String primaryName;

	private final Set<String> shortNames;
	private final Set<String> longNames;

	private final boolean obligatory;

	private final String valuePlaceholder;

	private final String description;
	private final OptionCallback<T> callback;

	/**
	 * Mohou použít pouze podtřídy
	 * 
	 * @param primaryName
	 * @param aliases
	 * @param obligatory
	 * @param description
	 * @param callback
	 */
	protected Option(String primaryName, Set<String> aliases, boolean obligatory,
			String valuePlaceholder, String description, OptionCallback<T> callback) {
		this.primaryName = primaryName;

		shortNames = new HashSet<String>();
		longNames = new HashSet<String>();

		addAlias(primaryName);
		for (String alias : aliases) {
			addAlias(alias);
		}

		this.valuePlaceholder = valuePlaceholder;
		this.description = description;
		this.obligatory = obligatory;
		this.callback = callback;

		logger.debug("creating option definition for: {}", primaryName.toString());
	}

	protected Option(Builder<T, ?> builder) {
		this(builder.getPrimaryName(), builder.getAliases(), builder.isObligatory(), builder
				.getValuePlaceholder(), builder.getDescription(), builder.getCallback());
	}

	public boolean supportsValue() {
		return false;
	}

	public T parseValue(String value) throws BadValueException {
		throw new UnsupportedOperationException("Class " + this.getClass().getName()
				+ " does not support values!");
	}

	private void addAlias(String alias) {
		if (alias.isEmpty()) {
			logger.debug("Not adding empty alias for " + primaryName);
		} else if (alias.length() <= MAX_SHORT_ALIAS_LENGHT) {
			logger.debug("Adding short alias for " + primaryName + " " + alias);
			shortNames.add(alias);
		} else {
			logger.debug("Adding long alias for " + primaryName + " " + alias);
			longNames.add(alias);
		}
	}

	public String getPrimaryName() {
		return primaryName;
	}

	/**
	 * poznamka: vsechna jmena jsou bez prefixu
	 */
	public Set<String> getShortNames() {
		return new HashSet<String>(shortNames);
	}

	/**
	 * poznamka: vsechna jmena jsou bez prefixu
	 */
	public Set<String> getLongNames() {
		return new HashSet<String>(longNames);
	}

	/**
	 * poznamka: vsechna jmena jsou bez prefixu
	 * 
	 * @return seznam vsech jmen a tedy sjednoceni {@link #getShortNames()} a
	 *         {@link #getLongNames()}
	 */
	public Set<String> getNames() {
		Set<String> names = new HashSet<String>();
		names.addAll(shortNames);
		names.addAll(longNames);
		return names;
	}

	public boolean isObligatory() {
		return obligatory;
	}

	public String getValuePlaceholder() {
		return valuePlaceholder;
	}

	public String getDescription() {
		return description;
	}

	public OptionCallback<T> getCallback() {
		return callback;
	}

	/**
     * Abstraktní builder pro potomky třídy {@link Option}
	 * 
	 * @param <T>
	 * @param <CHILD>
	 *            urcuje jakou tridu budou vracet settry (<a
	 *            href="http://en.wikipedia.org/wiki/Fluent_interface">fluent
	 *            interface design pattern </a>) - musi byt tedy na aktualni
	 *            tridu.
	 */
	@SuppressWarnings("unchecked")
	public abstract static class Builder<T, CHILD> {
		private String primaryName;
		private String valuePlaceholder = "";
		private String description = "";
		private OptionCallback<T> callback;

		private Set<String> aliases = new HashSet<String>();

		private boolean obligatory = false;

		public Builder(String primaryName, String valuePlaceholder) {
			this.primaryName = primaryName;
			this.valuePlaceholder = valuePlaceholder;
		}

		public Builder(String primaryName, String valuePlaceholder, boolean obligatory) {
			this.primaryName = primaryName;
			this.obligatory = obligatory;
			this.valuePlaceholder = valuePlaceholder;
		}

		public abstract Option<T> buildOption();

		public String getPrimaryName() {
			if (primaryName == null) {
				primaryName = "";
			}
			return primaryName;
		}

		public CHILD setPrimaryName(String primaryName) {
			this.primaryName = primaryName;
			return (CHILD) this;
		}

		public boolean isObligatory() {
			return obligatory;
		}

		public CHILD setObligatory(boolean obligatory) {
			this.obligatory = obligatory;
			return (CHILD) this;
		}

		public String getValuePlaceholder() {
			if (valuePlaceholder == null) {
				valuePlaceholder = "";
			}
			return valuePlaceholder;
		}

		public CHILD setValuePlaceholder(String placeholder) {
			valuePlaceholder = placeholder;
			return (CHILD) this;
		}

		public String getDescription() {
			if (description == null) {
				description = "";
			}
			return description;
		}

		public CHILD setDescription(String description) {
			this.description = description;
			return (CHILD) this;
		}

		public OptionCallback<T> getCallback() {
			return callback;
		}

		public CHILD setCallback(OptionCallback<T> callback) {
			this.callback = callback;
			return (CHILD) this;
		}

		public Set<String> getAliases() {
			if (aliases == null) {
				aliases = new HashSet<String>();
			}
			return aliases;
		}

		public CHILD setAliases(Set<String> aliases) {
			this.aliases = aliases;
			return (CHILD) this;
		}

		public CHILD addAlias(String alias) {
			getAliases().add(alias);
			return (CHILD) this;
		}

	}
}
